﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Threading;
using System.Diagnostics;
using System.Threading;

using LibSnesDotNet.Threading;
using LibSnesDotNet.Video;
using LibSnesDotNet.Audio;
using LibSnesDotNet.Input;
using LibSnesDotNet.UI;

using System.IO;

namespace LibSnesDotNet.Core
{
	class CoreDriver : IDisposable
	{
		Configuration _config;

		WorkerThread _workerThread;

		UIWindow          _uiWindow;
		Snes              _snes;
		IVideoDriver      _videoDriver;
		IAudioDriver      _audioDriver;
		InputAggregator   _inputAggregator;
		SnesJoypadButtons _buttonStates;

		bool _isAudioSynced;
		bool _isVideoSynced;

		volatile bool _isRunning;

		Stopwatch _stopwatch;
		int       _frameCount;

		public CoreDriver(UIWindow uiWindow, Configuration config)
		{
			IsDisposed = false;

			try
			{
				_config = config;

				_inputAggregator = new InputAggregator();
				_buttonStates = SnesJoypadButtons.None;

				_workerThread = new WorkerThread();

				_workerThread.DoWork(() =>
				{
					_uiWindow = uiWindow;

					_snes = new Snes();
					_snes.VideoUpdated += Snes_VideoUpdated;
					_snes.AudioUpdated += Snes_AudioUpdated;
					_snes.SetControllerPortDevice(1, SnesDevice.Joypad);

					_inputAggregator.InputReceived += InputAggregator_InputReceived;

					_isAudioSynced = true;
					_isVideoSynced = false;

					_isRunning = false;

					_stopwatch = new Stopwatch();
					_stopwatch.Start();
					_frameCount = 0;
				});
				_workerThread.WaitFor();
			}
			catch
			{
				Dispose();
				throw;
			}
		}

		public bool IsRunning { get { return _isRunning; } }

		public InputAggregator InputAggregator
		{
			get
			{
				AssertUndisposed();
				if (_isRunning) { throw new InvalidOperationException("Core must be stopped before accessing properties."); }

				return _inputAggregator;
			}
		}

		public Snes Snes
		{
			get
			{
				AssertUndisposed();
				if (_isRunning) { throw new InvalidOperationException("Core must be stopped before accessing properties."); }

				return _snes;
			}
		}

		public void SetDrivers(IVideoDriver videoDriver, IAudioDriver audioDriver)
		{
			AssertUndisposed();

			bool wasRunning = _isRunning;

			if (wasRunning) { Stop(); }
			_videoDriver = videoDriver;
			_audioDriver = audioDriver;
			if (wasRunning) { Start(); }
		}

		public void Start()
		{
			AssertUndisposed();

			if (_isRunning) { return; }

			_isRunning = true;

			_workerThread.DoWork(() =>
			{
				while (_isRunning)
				{
					_snes.RunToFrame();
					_frameCount++;

					/*if (_stopwatch.ElapsedMilliseconds >= 1000)
					{
						_uiWindow.Dispatcher.BeginInvoke(DispatcherPriority.Background,
						                                   (Action<int>)((c) => { _uiWindow.FpsCounter.Text = c.ToString(); }),
						                                   _frameCount);
						_frameCount = 0;
						_stopwatch.Reset();
						_stopwatch.Start();
					}*/
				}
			});
		}

		public void Stop()
		{
			AssertUndisposed();
			if (!_isRunning) { return; }

			_isRunning = false;
			_workerThread.WaitFor();
		}

		void Snes_VideoUpdated(object sender, VideoUpdatedEventArgs e)
		{
			if (_isVideoSynced)
			{
				_videoDriver.Render(e);
			}
			else
			{
				_videoDriver.TryRender(e);
			}
		}

		void Snes_AudioUpdated(object sender, AudioUpdatedEventArgs e)
		{
			if (_isAudioSynced)
			{
				_audioDriver.Play(e);
			}
			else
			{
				_audioDriver.TryPlay(e);
			}
		}

		void InputAggregator_InputReceived(object sender, InputReceivedEventArgs e)
		{
			var de = e as DigitalInputReceivedEventArgs;
			if (de == null || de.Input.ID.DeviceName != "Keyboard0") { return; }

			InputSection inputConfig = _config.Input;

			if (de.State)
			{
				if (inputConfig.Joypad1.Up.HasBindingTo(de.Input.ID)) { _buttonStates |= SnesJoypadButtons.Up; }
				if (inputConfig.Joypad1.Down.HasBindingTo(de.Input.ID)) { _buttonStates |= SnesJoypadButtons.Down; }
				if (inputConfig.Joypad1.Left.HasBindingTo(de.Input.ID)) { _buttonStates |= SnesJoypadButtons.Left; }
				if (inputConfig.Joypad1.Right.HasBindingTo(de.Input.ID)) { _buttonStates |= SnesJoypadButtons.Right; }
				if (inputConfig.Joypad1.B.HasBindingTo(de.Input.ID)) { _buttonStates |= SnesJoypadButtons.B; }
				if (inputConfig.Joypad1.A.HasBindingTo(de.Input.ID)) { _buttonStates |= SnesJoypadButtons.A; }
				if (inputConfig.Joypad1.Y.HasBindingTo(de.Input.ID)) { _buttonStates |= SnesJoypadButtons.Y; }
				if (inputConfig.Joypad1.X.HasBindingTo(de.Input.ID)) { _buttonStates |= SnesJoypadButtons.X; }
				if (inputConfig.Joypad1.L.HasBindingTo(de.Input.ID)) { _buttonStates |= SnesJoypadButtons.L; }
				if (inputConfig.Joypad1.R.HasBindingTo(de.Input.ID)) { _buttonStates |= SnesJoypadButtons.R; }
				if (inputConfig.Joypad1.Start.HasBindingTo(de.Input.ID)) { _buttonStates |= SnesJoypadButtons.Start; }
				if (inputConfig.Joypad1.Select.HasBindingTo(de.Input.ID)) { _buttonStates |= SnesJoypadButtons.Select; }

				_snes.SetInputState(1, 0, (int)_buttonStates, 0, 0);
			}
			else
			{
				if (inputConfig.Joypad1.Up.HasBindingTo(de.Input.ID)) { _buttonStates &= ~SnesJoypadButtons.Up; }
				if (inputConfig.Joypad1.Down.HasBindingTo(de.Input.ID)) { _buttonStates &= ~SnesJoypadButtons.Down; }
				if (inputConfig.Joypad1.Left.HasBindingTo(de.Input.ID)) { _buttonStates &= ~SnesJoypadButtons.Left; }
				if (inputConfig.Joypad1.Right.HasBindingTo(de.Input.ID)) { _buttonStates &= ~SnesJoypadButtons.Right; }
				if (inputConfig.Joypad1.B.HasBindingTo(de.Input.ID)) { _buttonStates &= ~SnesJoypadButtons.B; }
				if (inputConfig.Joypad1.A.HasBindingTo(de.Input.ID)) { _buttonStates &= ~SnesJoypadButtons.A; }
				if (inputConfig.Joypad1.Y.HasBindingTo(de.Input.ID)) { _buttonStates &= ~SnesJoypadButtons.Y; }
				if (inputConfig.Joypad1.X.HasBindingTo(de.Input.ID)) { _buttonStates &= ~SnesJoypadButtons.X; }
				if (inputConfig.Joypad1.L.HasBindingTo(de.Input.ID)) { _buttonStates &= ~SnesJoypadButtons.L; }
				if (inputConfig.Joypad1.R.HasBindingTo(de.Input.ID)) { _buttonStates &= ~SnesJoypadButtons.R; }
				if (inputConfig.Joypad1.Start.HasBindingTo(de.Input.ID)) { _buttonStates &= ~SnesJoypadButtons.Start; }
				if (inputConfig.Joypad1.Select.HasBindingTo(de.Input.ID)) { _buttonStates &= ~SnesJoypadButtons.Select; }

				_snes.SetInputState(1, 0, (int)_buttonStates, 0, 0);
			}
		}

		public bool IsDisposed { get; private set; }

		public void Dispose()
		{
			AssertUndisposed();
			IsDisposed = true;

			_isRunning = false;

			_workerThread.Dispose();
			_snes.Dispose();
		}

		public void AssertUndisposed()
		{
			if (IsDisposed) { throw new ObjectDisposedException(ToString()); }
		}
	}
}
